import  torch
from    torch import  nn
from    torch.nn import functional as F

#构建resnet基本单元
class BasicBlock(nn.Module):
    """
    resnet basic block
    """
    def __init__(self, ch_in, ch_out, stride=1):
        """

        :param ch_in:
        :param ch_out:
        """
        super(BasicBlock, self).__init__()

        # we add stride support for resbok, which is distinct from tutorials.
        self.conv1 = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=stride, padding=1)  #padding=1,kernel_size=3==>相当于same的效果，窗口大小取决于stride
        self.bn1 = nn.BatchNorm2d(ch_out)
        self.conv2 = nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1) #padding=1,kernel_size=3==>相当于same的效果，窗口不变
        self.bn2 = nn.BatchNorm2d(ch_out)

        self.extra = nn.Sequential() #定义跃迁单元x
        if stride != 1:  #通道数切换时，每层resnet交替之时，此时, stride=2，窗口数减半，跃迁x需要加1*1卷积
            # [b, ch_in, h, w] => [b, ch_out, h, w]
            self.extra = nn.Sequential(
                nn.Conv2d(ch_in, ch_out, kernel_size=1, stride=stride), #添加1*1卷积到跃迁模块x
                nn.BatchNorm2d(ch_out)
            )


    def forward(self, x):
        """

        :param x: [b, ch, h, w]
        :return:
        """
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        # short cut.
        # extra module: [b, ch_in, h, w] => [b, ch_out, h, w]
        # element-wise add:
        out = self.extra(x) + out
        out = F.relu(out)
        
        return out

#构建resnet网络: 是由多个resnet层组成
class ResNet(nn.Module):

    def __init__(self, layer_dims, num_classes=100): # [2, 2, 2, 2]
        super(ResNet, self).__init__()

        # 32 * 32 * 3 --> 10 * 10 * 64
        #初始卷积层
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, stride=3, padding=0), #窗口大小：ceil((32-3+2*0+1)/3)=10
            nn.BatchNorm2d(64)
        )
        #定义各层的resblock
        self.layer1 = self.build_resblock(64, 64,  layer_dims[0])   #首层的每一个Resnet模块: 跃迁=x, strides=1(默认)
        self.layer2 = self.build_resblock(64, 128, layer_dims[1], stride=2)
        self.layer3 = self.build_resblock(128, 256, layer_dims[2], stride=2)
        self.layer4 = self.build_resblock(256, 512, layer_dims[3], stride=2)

        self.outlayer = nn.Linear(512*1*1, 10)

    def forward(self, x):
        """

        :param x:
        :return:
        """
        x = F.relu(self.conv1(x))

        # [b, 64, h, w] => [b, 1024, h, w]
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)


        print('after conv:', x.shape) #[b, 512, 2, 2]
        # [b, 512, h, w] => [b, 512, 1, 1]
        #自适应平均池化, 随着输入的窗口大小，自动的设置池化核大小，只要保证最后的窗口为[1，1]即可
        x = F.adaptive_avg_pool2d(x, [1, 1])
        print('after pool:', x.shape)
        x = x.view(x.size(0), -1)  #变化维度，展开，进行全连接
        x = self.outlayer(x)
        return x

    #resnet层模块: 第一个基本模块strides=strides，第二个基本模块开始stride=1
    def build_resblock(self, in_ch, out_ch, blocks, stride=1):  #blocks:该层有多少个resnet基本模块；in_ch输入通道数, out_ch输出通道数

        res_blocks = nn.Sequential()
        # may down sample
        res_blocks.add_module('conv1', BasicBlock(in_ch, out_ch, stride))   #stride=步长,第一个基本模块strides=strides

        for i in range(1, blocks):
            res_blocks.add_module('conv2', BasicBlock(out_ch, out_ch, stride=1))   #stride=1第二个基本模块开始stride=1

        return res_blocks

def resnet18():
    return ResNet([2, 2, 2, 2])  #首个卷积层+(2+2+2+2)*2+全连接层=18


def resnet34():
    return ResNet([3, 4, 6, 3]) #首个卷积层+(3+4+6+3)*2+全连接层=34

def main():
    # 验证网络是否正确， 测试网络
    blk = BasicBlock(64, 128, stride=4)
    tmp = torch.randn(2, 64, 32, 32)
    out = blk(tmp)
    print('block:', out.shape)

    x = torch.randn(2, 3, 32, 32)
    model = resnet18()
    out = model(x)
    print('resnet18:', out.shape)


if __name__ == '__main__':
    main()